/* Copyright (c) 2009-2010, Sean A.O. Harney <sharney3@gmail.com> */

%{
#include "omf.h"


LLIST_P _tmp_args;//used in parsing. for target cmd and target dl arg lists

extern LLIST_P _current_func_asl;
extern int yylineno;

/* needed prototypes */
extern int yylex();

%}

%token TOK_ID TOK_CALL TOK_FUNCTION TOK_REPEAT
%token TOK_FUZZDATA TOK_PATTERN TOK_FILE TOK_RANDOM
%token TOK_TARGET TOK_COMMAND TOK_DL TOK_STDIN
%token TOK_SOCKET TOK_TCP TOK_UDP TOK_RAW TOK_SEND
%token TOK_EXPECT TOK_FLAG TOK_TERMINATION TOK_EXIT
%token TOK_SIGNAL TOK_ANY TOK_START TOK_PRINT TOK_INCLUDE
%token TOK_SHELL TOK_SLEEP
%token TOK_SLASH TOK_OPENPAREN TOK_CLOSEPAREN TOK_OPENBRACE
%token TOK_CLOSEBRACE TOK_COMMA TOK_SEMICOLON TOK_DOUBLEDOLLAR
%token TOK_NINT_CONST TOK_PINT_CONST TOK_FLOAT_CONST TOK_STRING_CONST

%union {
	char *string_val;
	int int_val;
	float float_val;
	char char_val;
	//bool bool_val;//wont compile
	int bool_val;
	struct st_asl_node *stmt;// ASL_NODE_P is typedefed to same 
	struct st_llist *stmts;//llist of Asl_node
	struct st_dlarg *dlarg;
}


%type <string_val> TOK_DOUBLEDOLLAR
%type <string_val> TOK_STRING_CONST
%type <string_val> TOK_ID
%type <int_val> TOK_NINT_CONST
%type <int_val> TOK_PINT_CONST
%type <float_val> TOK_FLOAT_CONST

%type <stmt> Stmt Stmt_Fuzzdata Stmt_Fuzzdata_Random Stmt_Fuzzdata_File
%type <stmt> Stmt_Fuzzdata_Pattern
%type <stmt> Stmt_Start
%type <stmt> Stmt_Target
%type <stmt> Stmt_Target_Socket Stmt_Target_Socket_Tcp Stmt_Target_Socket_Udp
%type <stmt> Stmt_Target_Dl
%type <stmt> Stmt_Target_Command
%type <stmt> Stmt_Send Stmt_Expect Stmt_Function_Call Stmt_Print
%type <stmt> Stmt_Include Stmt_Shell
%type <stmt> Stmt_Flag Stmt_Flag_Termination
%type <stmt> Stmt_Flag_Termination_Exit Stmt_Flag_Termination_Signal
%type <stmt> Stmt_Flag_Termination_Any
%type <stmt> Stmt_Repeat
%type <stmt> Stmt_Sleep

%type <stmts> Stmts Block Function_Decl
%type <bool_val> Opt_Stdin
%type <dlarg> TSDl_Arg 

%%

OMFdef : StmtsAndFuncs ;


/* All statements not contained in an (explicitly declared) function must
 * come before (on a previous line, or sharing a line) the first function
 * declaration. IOW:
 * 	0 or more stmts, or 0 or more stmts and 0 or more func declarations.
 * This is enforced by the grammar directly. */
StmtsAndFuncs : StmtsAndFuncs Function_Decl
| Stmts
{
    LLIST_P asl = $1;

	if(asl != NULL)
	{
	 /* ASL is full of stmts which are not in any function. */

    		LLIST_P asl_main = asl_get_asl("main");
		if(asl_main == NULL) 
		{
	    		debug2_printf("StmtsAndFuncs: Adding new ASL [%s]\n","main");
			asl_add_new_asl("main", asl, glob.parser_current_filename);

		}
		else
		{
		/* doesn't happen. Grammar enforces that main will be declared implicitly before explicitly, or solely
		  * explicitly , or solely implicitly*/
		  yyerror("Somehow main declared explicitly before implicitly. This is impossible!");
		}
	current_func_asl_clear();
	}//else do nothingg, NULL stmtsAndFuncs (type llist of ASL_NODE_P stmts) is from empty stmt
} ;


Function_Decl : TOK_FUNCTION TOK_ID Block
{
    char *symname = $2;
    LLIST_P asl = $3;

    if(!strcmp(symname, "main"))
    {
    LLIST_P asl_main = asl_get_asl("main");
	if(asl_main != NULL) /* exists already,main was declared implicitly */
	{
   		if(glob.parser_explicit_main == true)
			yyerror("main was already explicitly declared. Cannot explicitly declare a function more than once");
		 else
		 {
	 		LLIST_P walker;
    			ASL_NODE_P stmt;

	    		debug2_printf("main was already implicitly declared. Combining impicit and explicit ASLs\n");
	     		/* **append** statements stmts to main */ 
			for(walker = asl; walker != NULL; walker=walker->next)
			{
			    stmt = walker->element;
			    asl_main = asl_append(asl_main, stmt);
			}
		 }
	}
	else
	{
		glob.parser_explicit_main = true;
		debug2_printf("Adding new ASL [%s]\n","main");
	//	asl_add_new_asl("main", asl);
			asl_add_new_asl("main", asl, glob.parser_current_filename);


    	}
    }
    else
    {
		if(asl_get_asl(symname) != NULL)
			yyerror_println("%s was already explicitly declared. Cannot explicitly declare a function more than once",symname);
		else
		{
	    ASL_NODE_P first_stmt = asl->element;
		    debug2_printf("Function_Decl: Adding new ASL [%s]\n",symname);
		//asl_add_new_asl(symname, asl);
			asl_add_new_asl(symname, asl, first_stmt->filename);
		}
    }
} ;

Block : TOK_OPENBRACE Stmts TOK_CLOSEBRACE
{
	$$ = $2;
	current_func_asl_clear(); /* NOTE: this is needed here */
}

Stmts : Stmts Stmt TOK_SEMICOLON
{
    	if($2 != NULL) /* not an empty statement ($$ = NULL; action below) (and also not an empty `Stmts`)  */
    	{
	/* add Stmt to ASL, if _current_func_asl is NULL then new asl created */
	_current_func_asl=asl_append(_current_func_asl,$2);
	}
	$$=_current_func_asl;
}
| /* epsilon */
{
		$$ = NULL; /* needed so that above asl_append does not happen.Needed for func decl with no statements symname{} */
	}
	;

Stmt : Stmt_Fuzzdata 
	| Stmt_Target
	| Stmt_Start
	| Stmt_Flag
	| Stmt_Send
	| Stmt_Expect
	| Stmt_Function_Call
	| Stmt_Print
	| Stmt_Include
	| Stmt_Shell
	| Stmt_Repeat
	| Stmt_Sleep
| /* epsilon, needed to allow empty stmts, eg ;;;; */
{
		$$ = NULL; /* empty statement returns NULL */
} ;

Stmt_Fuzzdata : Stmt_Fuzzdata_Pattern
		| Stmt_Fuzzdata_File
		| Stmt_Fuzzdata_Random ;

Stmt_Fuzzdata_Pattern : TOK_FUZZDATA TOK_PATTERN TOK_STRING_CONST TOK_SLASH TOK_PINT_CONST
{
	$$ = new_stmt_fuzzdata_pattern($3, $5);//pattern,pattern_maxlen
} ;

Stmt_Fuzzdata_File : TOK_FUZZDATA TOK_FILE TOK_STRING_CONST
{
	$$ = new_stmt_fuzzdata_file($3);
} ;

Stmt_Fuzzdata_Random : TOK_FUZZDATA TOK_RANDOM
{
	$$ = new_stmt_fuzzdata_random(0,0);/* lb=ub=0 => use defaults */
}
|
TOK_FUZZDATA TOK_RANDOM TOK_PINT_CONST TOK_COMMA TOK_PINT_CONST
{
	$$ = new_stmt_fuzzdata_random($3, $5);//lb,ub
} ;



Stmt_Target : Stmt_Target_Command
	| Stmt_Target_Dl
	| Stmt_Target_Socket
	;


TSC_Args : TOK_DOUBLEDOLLAR TSC_Args
{ 
	 _tmp_args = llist_prepend($1, strlen($1) + 1, _tmp_args);//list gets built up backwards, hence _prepend.



}
| TOK_STRING_CONST TSC_Args
{ 
	 _tmp_args = llist_prepend($1, strlen($1) + 1, _tmp_args);//list gets built up backwards, hence _prepend.
}
| /* epsilon */ ; 


Stmt_Target_Command : TOK_TARGET TOK_COMMAND Opt_Stdin TOK_STRING_CONST TSC_Args
{
    	bool command_stdin;
	int ind_dollar;
	char *arg0;

	command_stdin = $3;
	arg0 = $4;
	 _tmp_args = llist_prepend(arg0, strlen(arg0) + 1, _tmp_args);// prepend argv[0] ($4) to list
	ind_dollar = llist_str_index("$$", _tmp_args);//-1 if not found

/* best to catch these now at parse-time than at process-time. It would be possible to do it in
 * the grammar, however this would make the grammar have more non-terminals and rules and be more
 * complex. It is also good to print a specific error message as done below rather than enforce
 * syntax with only the grammar which would give a less descriptive yyerror().
 */
	if(command_stdin == true && ind_dollar != -1)
		yyerror("Cannot use $$ with `target command stdin ...`.");
	if(command_stdin == false && ind_dollar == -1)
		   yyerror("Must use $$ inside `target command ...` statement when not using `target command stdin ...`.\n");

	$$ = new_stmt_target_command(_tmp_args, ind_dollar, command_stdin);
	_tmp_args = NULL; /* NOTE: do not free the old _tmp_args llist as pointer references to it will still be in use.
			   * Will cause a new args list to be build on 2nd,3rd,4th,etc target command parse.
			  */
} ;

Opt_Stdin : TOK_STDIN
{
	$$ = true;
}
|  /* epsilon */
{
	$$ = false;
}

TSDl_Arg :  TOK_STRING_CONST
{ 
	$$ = new_dlarg_cstring_ptr($1);
}
|  TOK_PINT_CONST
{ 
	$$ = new_dlarg_int($1);
}
|  TOK_NINT_CONST
{ 
	 $$ = new_dlarg_int($1);
}
|  TOK_FLOAT_CONST
{ 
	 $$ = new_dlarg_float($1);
}
|  TOK_DOUBLEDOLLAR
{ 
	 $$ = new_dlarg_generic_ptr("$$",3 /* strlen("$$) + 1 */);
} ;

TSDl_Args : TSDl_Args TSDl_Arg
{
    	DL_ARG_P dlarg = $2;
	 _tmp_args = llist_prepend(dlarg, sizeof(struct st_dlarg), _tmp_args);//list gets built up backwards, hence _prepend.
}
| /* epsilon */
Stmt_Target_Dl : TOK_TARGET TOK_DL TOK_STRING_CONST TOK_STRING_CONST TOK_OPENPAREN TSDl_Args TOK_CLOSEPAREN
{
    char *libfname = $3;
    char *symbol = $4;
    int i;
    int ind_dollar = -1;
    struct st_dlarg *dlarg;
    LLIST_P walker;


	//find the $$ if any
	for(i = 0, walker = _tmp_args; walker != NULL; walker = walker->next, i++)
	{
		dlarg = walker->element;
		if(dlarg->type == TYPE_GENERIC_PTR && dlarg->ptr_len == 3 && !strncmp(dlarg->val.ptr_val, "$$", 2))
		{
		    ind_dollar = i;
		    break;
		}
	}

	$$ = new_stmt_target_dl(_tmp_args, ind_dollar, libfname, symbol);
	_tmp_args = NULL; /* see NOTE about _tmp_args in Stmt_Target_Command */
} ;

Stmt_Target_Socket : Stmt_Target_Socket_Tcp
	| Stmt_Target_Socket_Udp
	;// Stmt_Target_Socket_Raw

Stmt_Target_Socket_Tcp : TOK_TARGET TOK_SOCKET TOK_TCP TOK_STRING_CONST TOK_PINT_CONST
{
	$$ = new_stmt_target_socket($4, $5, SOCK_STREAM);
}

Stmt_Target_Socket_Udp : TOK_TARGET TOK_SOCKET TOK_UDP TOK_STRING_CONST TOK_PINT_CONST
{
	$$ = new_stmt_target_socket($4, $5, SOCK_DGRAM);
}

Stmt_Start : TOK_START
{
	$$ = new_stmt_start(-1);//-1 => default start_args

	/*
	INTERACTIVE_MODE_ENTRANCE
    	assert_fuzzdata_stmt_used("start");	
	assert_target_stmt_used("start");	
	do_start_stmt(-1);
	INTERACTIVE_MODE_EXIT
	*/
}
	| TOK_START TOK_PINT_CONST
{
	$$ = new_stmt_start($2);
} ;

Stmt_Flag : Stmt_Flag_Termination ;
	/* TODO: come up with some other ones. */


Stmt_Flag_Termination : Stmt_Flag_Termination_Exit 
		      | Stmt_Flag_Termination_Signal
		      | Stmt_Flag_Termination_Any ;

Stmt_Flag_Termination_Exit : TOK_FLAG TOK_TERMINATION TOK_EXIT TOK_PINT_CONST
{
	$$ = new_stmt_flag_termination_exit($4);
} 
| TOK_FLAG TOK_TERMINATION TOK_EXIT TOK_NINT_CONST
{
	$$ = new_stmt_flag_termination_exit($4);
}
| TOK_FLAG TOK_TERMINATION TOK_EXIT TOK_ANY
{
	$$ = new_stmt_flag_termination_exit_any();
} ;

Stmt_Flag_Termination_Signal : TOK_FLAG TOK_TERMINATION TOK_SIGNAL TOK_PINT_CONST
{
	$$ = new_stmt_flag_termination_signal($4);
} 
| TOK_FLAG TOK_TERMINATION TOK_SIGNAL TOK_ANY
{
	$$ = new_stmt_flag_termination_signal_any();
} ;

Stmt_Flag_Termination_Any : TOK_FLAG TOK_TERMINATION TOK_ANY
{
	$$ = new_stmt_flag_termination_any();
} ;

Stmt_Send : TOK_SEND TOK_STRING_CONST
{
	$$ = new_stmt_send_str($2);
}
	| TOK_SEND TOK_DOUBLEDOLLAR
{
	$$ = new_stmt_send_str($2);//"$$"
}
	;

Stmt_Expect : TOK_EXPECT Opt_Cases Opt_Default_Case
{
	$$ = new_stmt_expect();
}

Opt_Cases : Opt_Cases TOK_STRING_CONST Block
| /* epsilon */ ; 

Opt_Default_Case : Block
| /* epsilon */ ; 

Stmt_Function_Call : TOK_CALL TOK_ID
{
	$$ = new_stmt_functioncall($2);
} ;

Stmt_Print : TOK_PRINT TOK_STRING_CONST
{
	$$ = new_stmt_print($2);
} ;

Stmt_Include : TOK_INCLUDE TOK_STRING_CONST
{
	$$ = new_stmt_include($2);
} ;

Stmt_Shell : TOK_SHELL TOK_STRING_CONST
{
	$$ = new_stmt_shell($2);
} ;

Stmt_Repeat : TOK_REPEAT TOK_PINT_CONST Stmt
{
	$$ = new_stmt_repeat_stmt($2, $3);
}
	| TOK_REPEAT Stmt
{
	$$ = new_stmt_repeat_stmt(-1, $2);//-1 => infinite loop
	// new_stmt_repeat arg is type int so using with $2 (yylval.int_val) leads to no loss in precision.
} ;


Stmt_Sleep : TOK_SLEEP TOK_PINT_CONST
{
	$$ = new_stmt_sleep($2);
}
|	TOK_SLEEP TOK_STRING_CONST
{
    char *str = $2;
    /* inspired by sleep(1) linux cmd */
        if(strlen(str) == 0)
	yyerror("Sleep \"\" not allowed. Cannot used sleep with empty string!");
    else
    {
	int i;
	int value,mult;
	char *p = str + strlen(str) - 1;
	char suffix = *p;


	*p = '\0';/* tie off str. p is ptr into str */

// convert to seconds
	mult = 1;
	switch(suffix)
	{
	case 'd':
		mult *= 24; /* 24*60*60 s */
		/* fall through */
	case 'h':
		mult *= 60; /* 60*60 s */
		/* fall through */
	case 'm':
	mult *= 60; /* 60s */
 	case 's':
	/* value *= 1; 1s */
	i = atoi(str); //TODO: eror check;
	value = mult * i;
	$$ = new_stmt_sleep(value);
	break;
	default:
	yyerror_println("invalid sleep time: %s%c: invalid suffix '%d'", str, suffix, suffix);
	}
    }
} ;

%%
